package edu.dublbo.generator.code.dao;

import edu.dublbo.generator.basic.entity.TDemoModel;
import edu.dublbo.generator.basic.entity.TDemoModelDetail;
import edu.dublbo.generator.code.bean.*;
import edu.dublbo.generator.common.exception.OptErrorException;
import edu.dublbo.generator.common.result.OptStatus;
import edu.dublbo.generator.utils.FileOperator;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author DubLBo
 * @since 2020-09-08 20:35
 * i believe i can i do
 */
public class CodeFileGenDAO {
    private static final Logger logger = LoggerFactory.getLogger(CodeFileGenDAO.class);

    // 作者
    private static final String AUTHOR = "habitplus";
    // 时间格式
    private static final String DATE_FORMAT = "yyyy年M月d日 H:m:s.S";
    // 模板名
    private static final String TEMPLATE_DIR = "src/main/resources/templates/";
    private static final String FILE_NAME_TABLE = "table.ftl";
    private static final String FILE_NAME_MODEL = "model.ftl";
    private static final String FILE_NAME_MAPPER_INTER = "mapper_inter.ftl";
    private static final String FILE_NAME_MAPPER_XML = "mapper_xml.ftl";
    private static final String FILE_NAME_SERVICE = "service.ftl";
    private static final String FILE_NAME_CONTROLLER = "controller.ftl";

    private static final String CACHE_FILE_PATH = "src/main/resources/cache/cache";

    public CodeFileGenDAO() {}


    private static synchronized List<String> generateDemo(String templateFilePath, Map<String, Object> dataMap) {
        // 1. 创建 FreeMarker 配置实例
        Configuration conf = new Configuration(Configuration.VERSION_2_3_0);
        Writer writer = null;
        List<String> res;
        try {
            // 2. 设置模板文件，并加载模板文件
            Template template = conf.getTemplate(templateFilePath);

            // 3. 生成数据
            File docFile = new File(CACHE_FILE_PATH);
            // 为了确保生成文件的目录存在
            ensureParentFileExist(docFile);
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(docFile)));
            // 4. 输出文件
            template.process(dataMap, writer);
            res = FileOperator.readContent(docFile);
            logger.info("以【{}】为模板的文件已生成！！！", templateFilePath);
        } catch (Exception e) {
            logger.error("模板文件【{}】错误！", templateFilePath);
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "模板文件错误");
        } finally {
            if (writer != null) {
                try {
                    writer.flush();
                } catch (IOException e) {
                    throw new OptErrorException(OptStatus.FAIL.getOptCode(), "关闭流出现错误");
                }
            }
        }
        return res;
    }

    private static void ensureParentFileExist(File docFile) {
        File fileParent = docFile.getParentFile();
        if (!fileParent.exists()) {
            fileParent.mkdirs();
        }
    }

    /**
     * 获取当前系统时间
     *
     * @return yyyy年M月d日 H:m:s.S
     */
    private static String getCurTimeStr() {
        Date curDate = new Date();
        return new SimpleDateFormat(DATE_FORMAT).format(curDate);
    }

    /**
     * 根据模型名获得业务类名称（service，controller）
     *
     * @param modelName 模型名
     * @return 模型的业务名
     */
    public static String getBusiBeanName(String modelName) {
        if (StringUtils.isBlank(modelName)) {
            logger.error("model Name is EMPTY #233#");
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "模型名为空");
        }
        if (modelName.length() < 3) {
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "模型名长度不合适");
        }
        char first = modelName.charAt(0);
        if (first == 'T' || first == 'V' || first == 'S') {
            return modelName.substring(1, 2).toUpperCase() + modelName.substring(2);
        }
        return modelName.substring(0, 1).toUpperCase() + modelName.substring(1);
    }

    /**
     * 生成代码，用于代码浏览
     * @param model 模型
     * @param details 模型明细列表
     * @return 结果 Map
     */
    public static Map<String, Object> generateCode(TDemoModel model, List<TDemoModelDetail> details) {
        if (model == null || details == null || details.size() == 0) {
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "模型数据丢失，请刷新后重试");
        }

        Map<String, Object> dataMap = initBaseData(model, details);

        List<String> tableStructureDemo = generateDemo(TEMPLATE_DIR + FILE_NAME_TABLE, dataMap);
        List<String> entityDemo = generateDemo(TEMPLATE_DIR + FILE_NAME_MODEL, dataMap);
        List<String> mapperInterfaceDemo = generateDemo(TEMPLATE_DIR + FILE_NAME_MAPPER_INTER, dataMap);
        List<String> mapperXmlDemo = generateDemo(TEMPLATE_DIR + FILE_NAME_MAPPER_XML, dataMap);
        List<String> serviceDemo = generateDemo(TEMPLATE_DIR + FILE_NAME_SERVICE, dataMap);
        List<String> controllerDemo = generateDemo(TEMPLATE_DIR + FILE_NAME_CONTROLLER, dataMap);

        Map<String, Object> res = new HashMap<>();
        res.put("tableStructureDemo", String.join("", tableStructureDemo));
        res.put("entityDemo", String.join("", entityDemo));
        res.put("mapperInterfaceDemo", String.join("", mapperInterfaceDemo));
        res.put("mapperXmlDemo", String.join("", mapperXmlDemo));
        res.put("serviceDemo", String.join("", serviceDemo));
        res.put("controllerDemo", String.join("", controllerDemo));

        return res;
    }

    /**
     * 生成代码并压缩，包括：
     * <pre>
     *      1. table 脚本
     *      2. java 实体类
     *      3. mapper 层：mapper接口类 和 mapper xml文件
     *      4. service 类
     *      5. controller 类
     * </pre>
     *
     * @param model        模型信息
     * @param modelDetails 模型明细列表：属性 - 表字段
     * @param zos          压缩文件输出流
     */
    public static void zipCode(TDemoModel model, List<TDemoModelDetail> modelDetails, ZipOutputStream zos) {
        // 检查数据
        if (model == null || modelDetails == null || modelDetails.size() == 0) {
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "模型数据丢失，请刷新后重试");
        }

        // FreeMarker 配置
        Configuration conf = new Configuration(Configuration.VERSION_2_3_0);
        List<String> templatePaths = loadTemplate();

        String modelName = model.getModelName();
        String packageName = model.getPackageDir();
        String busiName = getBusiBeanName(modelName);
        try {
            for (String templatePath : templatePaths) {// 基础数据配置
                Map<String, Object> dataMap = initBaseData(model, modelDetails);
                // 加载模板
                StringWriter sw = new StringWriter();
                Template template = conf.getTemplate(templatePath);
                template.process(dataMap, sw);
                // 添加到 zip
                String filename = getFileName(templatePath, modelName, busiName, packageName);
                if (StringUtils.isEmpty(filename)) {
                    logger.error("获取模板【{}】对应的生成文件的文件名错误", templatePath);
                    throw new OptErrorException(OptStatus.FAIL.getOptCode(), "获取生成文件的文件名错误");
                }
                zos.putNextEntry(new ZipEntry(filename));
                IOUtils.write(sw.toString(), zos, StandardCharsets.UTF_8);
                zos.closeEntry();
                sw.close();
            }
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "模板文件读取错误");
        }

    }

    private static String getRequestPath(String moduleName, String busiName) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(moduleName)) sb.append("/").append(moduleName);
        for (char c : busiName.toCharArray()) {
            if (c >= 'A' && c <= 'Z') {
                sb.append("/").append((char) (c + 32));
            } else {
                sb.append(c);
            }
        }
        return sb.length() == 0 ? "/" : sb.toString();
    }

    private static Map<String, Object> initBaseData(TDemoModel model, List<TDemoModelDetail> modelDetails) {
        ModelEntity entity = new ModelEntity();
        String modelName = model.getModelName();
        String busiName = getBusiBeanName(modelName);
        String packageName = model.getPackageDir();
        entity.setModelName(modelName);
        entity.setModelDesc(model.getRemark());
        entity.setTableName(model.getTableName());
        entity.setModelPackageDir(model.getPackageDir());
        entity.setMapperName(modelName + "Mapper");
        entity.setServiceName(busiName + "Service");
        entity.setControllerName(busiName + "Controller");

        String moduleName = packageName.substring(packageName.lastIndexOf('.') + 1);
        entity.setModuleName(moduleName);
        entity.setRequestPath(getRequestPath(moduleName, busiName));

        Set<String> set = new HashSet<>();
        List<ModelDetailEntity> list = new ArrayList<>();
        for (TDemoModelDetail de : modelDetails) {
            ModelDetailEntity e = new ModelDetailEntity();
            e.setProName(de.getPropertyName());
            e.setColName(de.getColumnName());
            e.setColDesc(de.getRemark());
            e.setProType(de.getProType());

            // 设置字段类型的长度
            StringBuilder tmp = new StringBuilder();
            tmp.append(de.getColType());
            int colLen = de.getColumnLength() == null ? 0 : de.getColumnLength();
            int defaultLen = de.getDefaultColLen() == null ? 0 : de.getDefaultColLen();
            if (colLen > 0) {
                // 设置了字段长度
                tmp.append("(").append(colLen).append(")");
            } else if (defaultLen > 0) {
                // 默认长度不为空
                tmp.append("(").append(defaultLen).append(")");
            }
            e.setColType(tmp.toString());

            // 需要导入额外的包
            String p = de.getQualifiedProType();
            if (!p.startsWith("java.lang")) set.add(p);

            list.add(e);
        }
        entity.setDetails(list);
        entity.setImportPackageSet(set);

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("model", entity);
        dataMap.put("author", AUTHOR);
        dataMap.put("createTime", getCurTimeStr());
        return dataMap;
    }

    /**
     * 获取文件名
     */
    private static String getFileName(String tempPath, String modelName, String busiName, String packageName) {

        if (StringUtils.isBlank(packageName)) {
            throw new OptErrorException(OptStatus.FAIL.getOptCode(), "包路径为空");
        }
        String packagePath = "main/java/" + packageName.replace(".", "/");

        if (tempPath.endsWith(FILE_NAME_TABLE)) {
            return packagePath + "/model/" + modelName + "Mysql.sql";
        }

        if (tempPath.endsWith(FILE_NAME_MODEL)) {
            return packagePath + "/model/" + modelName + ".java";
        }

        if (tempPath.endsWith(FILE_NAME_MAPPER_INTER)) {
            return packagePath + "/mapper/" + modelName + "Mapper.java";
        }

        if (tempPath.endsWith(FILE_NAME_MAPPER_XML)) {
            return "main/resources/mapper/mysql/" + modelName + "Mapper.xml";
        }

        if (tempPath.endsWith(FILE_NAME_SERVICE)) {
            return packagePath + "/service/" + busiName + "Service.java";
        }

        if (tempPath.endsWith(FILE_NAME_CONTROLLER)) {
            return packagePath + "/controller/" + busiName + "Controller.java";
        }

        throw new OptErrorException(OptStatus.FAIL.getOptCode(), "该模板未定义路径");
    }

    private static List<String> loadTemplate() {
        List<String> res = new ArrayList<>();
        res.add(TEMPLATE_DIR + FILE_NAME_TABLE);
        res.add(TEMPLATE_DIR + FILE_NAME_MODEL);
        res.add(TEMPLATE_DIR + FILE_NAME_MAPPER_INTER);
        res.add(TEMPLATE_DIR + FILE_NAME_MAPPER_XML);
        res.add(TEMPLATE_DIR + FILE_NAME_SERVICE);
        res.add(TEMPLATE_DIR + FILE_NAME_CONTROLLER);
        return res;
    }

}
